package io.cici.cc.mybatis.lite.sql_source.scripting.tag;

import io.cici.cc.mybatis.lite.session.Configuration;
import io.cici.cc.mybatis.lite.sql_source.Context;
import io.cici.cc.mybatis.lite.sql_source.scripting.ognl.ExpressionEvaluator;
import io.cici.cc.mybatis.lite.sql_source.scripting.tag.forEach.FilteredContext;
import io.cici.cc.mybatis.lite.sql_source.scripting.tag.forEach.SeparatorContext;

import java.util.Map;


public class ForEachTag implements Tag {
    public static final String ITEM_PREFIX = "__frch_";
    public final Configuration configuration;
    private final ExpressionEvaluator expressionEvaluator;
    private final String collectionExpression;

    private final Tag dynamicTags;
    private final String open;
    private final String close;
    private final String separator;
    private final String item;
    private final String index;


    public ForEachTag(Configuration configuration, Tag dynamicTags, String collectionExpression, String index, String item, String open, String close, String separator) {
        this.expressionEvaluator = new ExpressionEvaluator();
        this.collectionExpression = collectionExpression;

        this.dynamicTags = dynamicTags;
        this.open = open;
        this.close = close;
        this.separator = separator;
        this.index = index;
        this.item = item;
        this.configuration = configuration;
    }

    public static String itemizeItem(String item, int i) {
        return ITEM_PREFIX + item + "_" + i;
    }

    @Override
    public boolean apply(Context context) {
        Map<String, Object> bindings = context.getBindings();
        final Iterable<?> iterable = expressionEvaluator.evaluateIterable(bindings, collectionExpression);
        if (iterable == null || !iterable.iterator().hasNext()) {
            return true;
        }
        boolean first = true;
        applyOpen(context);
        int i = 0;
        for (Object o : iterable) {
            Context oldContext = context;
            if (first) {
                context = new SeparatorContext(this, context, "");
            } else {
                context = new SeparatorContext(this, context, separator);
            }
            int uniqueNumber = context.getUniqueNumber();

            if (o instanceof Map.Entry) {

                Map.Entry<Object, Object> mapEntry = (Map.Entry<Object, Object>) o;
                applyIndex(context, mapEntry.getKey(), uniqueNumber);
                applyItem(context, mapEntry.getValue(), uniqueNumber);
            } else {
                applyIndex(context, i, uniqueNumber);
                applyItem(context, o, uniqueNumber);
            }
            dynamicTags.apply(new FilteredContext(configuration, context, index, item, uniqueNumber));
            if (first) {
                first = !((SeparatorContext) context).isPrefixApplied();
            }
            context = oldContext;
            i++;
        }
        applyClose(context);
        context.getBindings().remove(item);
        context.getBindings().remove(index);
        return true;
    }

    private void applyIndex(Context context, Object o, int i) {
        if (index != null) {
            context.bind(index, o);
            context.bind(itemizeItem(index, i), o);
        }
    }

    private void applyItem(Context context, Object o, int i) {
        if (item != null) {
            context.bind(item, o);
            context.bind(itemizeItem(item, i), o);
        }
    }

    private void applyOpen(Context context) {
        if (open != null) {
            context.appendSql(open);
        }
    }

    private void applyClose(Context context) {
        if (close != null) {
            context.appendSql(close);
        }
    }

}
